package com.spx.kernel.service;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;

import org.hibernate.ejb.Ejb3Configuration;

import com.spx.kernel.types.ModelType;

public class PersistServiceImpl implements PersistService
{

	Logger logger = Logger.getLogger(this.getClass().getName());

	private final Set<Class<? extends ModelType>> types = new HashSet<Class<? extends ModelType>>();

	private Properties properties = new Properties();

	private EntityManagerFactory entityManagerFactory;

	private ThreadLocal<EntityTransaction> entityTransaction = new ThreadLocal<EntityTransaction>();
	private final ThreadLocal<Integer> txStack = new ThreadLocal<Integer>();

	private EntityManager entityManager;

	public PersistServiceImpl()
	{

	}

	private EntityManagerFactory getEntityManagerFactory()
	{
		if (entityManagerFactory == null)
		{
			logger.log(Level.INFO, "Entity Manager Factory Creation");
			final Ejb3Configuration configuration = new Ejb3Configuration();

			configuration.setProperties(properties);
			for (final Class<?> clazz : this.types)
			{
				configuration.addAnnotatedClass(clazz);
				logger.info("Adding  type " + clazz.getName());
			}

			if (properties.getProperty("hibernate.connection.driver_class") != null
					&& properties.getProperty("hibernate.connection.driver_class").trim().length() > 0)
			{
				try
				{
					entityManagerFactory = configuration.buildEntityManagerFactory();
				} catch (Exception e)
				{
					logger.warning("Entity Manager Creation Failure: NO database connection  possible");
					e.printStackTrace();
				}
			}
		}
		return entityManagerFactory;
	}

	public void start()
	{
		entityTransaction = new ThreadLocal<EntityTransaction>();
		getEntityManager();
		logger.info("Persist Service  started");
	}

	public EntityManager getEntityManager()
	{
		if (entityManager == null)
		{
			final EntityManagerFactory intialEntityManagerFactory = getEntityManagerFactory();
			if (intialEntityManagerFactory != null)
			{
				entityManager = intialEntityManagerFactory.createEntityManager();
			}
		}

		return entityManager;
	}

	public void begin()
	{
		logger.info("begin start");
		Integer tx = txStack.get();
		if (tx == null || tx == 0)
		{
			tx = 0;
			runBegin();
		} 
		tx++;
		txStack.set(tx);
		logger.info("---->Counter is " + tx);
	}

	private void runBegin()
	{
		EntityTransaction transaction = entityTransaction.get();
		if (transaction == null || transaction.isActive() == false)
		{
			if (getEntityManager() != null)
			{
			  
				transaction = getEntityManager().getTransaction();
				
				transaction.begin();
				entityTransaction.set(transaction);
				logger.log(Level.INFO, "TX started");
			}
		}
	}

	public void commit()
	{
		// //// THIS DEOS NOT WORK!!!!!!!!!!!!!!!!!
		System.out.println("commit start");
		Integer tx = txStack.get();
		
		if (tx== null)
		{
		   tx = 0;
		}
		else 
		{
		    tx--;
		}
		
		if ( tx <= 0)
		{
			tx = 0;
			runCommit();
		}
		 


		txStack.set(tx);
		 System.out.println("<----Counter is " + tx);
	}

	private void runCommit()
	{
		final EntityTransaction transaction = entityTransaction.get();
		if (transaction != null && transaction.isActive())
		{
			try
			{
				transaction.commit();

				entityTransaction.set(null);
				logger.log(Level.INFO, "TX commited");
			} catch (Throwable e)
			{
				logger.log(Level.SEVERE, "TX commit failure", e);
				rollback();
				throw new RuntimeException(e);
			}

		}
	}

	public void rollback()
	{
		logger.warning("TX:ROLLBACK");

		runRollback();
	}

	private void runRollback()
	{
		final EntityTransaction transaction = entityTransaction.get();
		if (transaction != null && transaction.isActive())
		{
			transaction.rollback();
			txStack.set(null);
			entityTransaction.set(null);
			logger.log(Level.INFO, "TX roll back");
		}
	}

	@Override
	public void addType(final Class<? extends ModelType> clazz)
	{
		types.add(clazz);
		entityManagerFactory = null;
	}

	@Override
	public void persist(final ModelType persistant)
	{
		if (getEntityManager() != null)
		{
			getEntityManager().persist(persistant);

		} else
		{
			this.logger.warning("Persistance Entity Manager non - functional");
		}
		// fireUpdates((ModelType) persistant);
	}

	@Override
	public void setProperties(Properties properties)
	{
		this.properties = properties;
	}

	@Override
	public void addTypes(Set<Class<? extends ModelType>> classes)
	{
		for (final Class<? extends ModelType> clazz : classes)
		{
			addType(clazz);
		}

	}

	@Override
	public Set<Class<? extends ModelType>> getTypes()
	{
		return this.types;
	}

	@Override
	public void rename(ModelType model, String newName)
	{
		if (findByName(newName) == null)
		{
			model.setName(newName);
			update(model);
		}

	}

	@Override
	public void update(ModelType model)
	{
		getEntityManager().merge(model);

		fireUpdates(model);
	}

	Map<String, UpdateListener> ears = new TreeMap<String, UpdateListener>();

	@Override
	public <T extends ModelType> void addChangeListener(Class<T> clazz, UpdateListener l)
	{
		ears.put(clazz.getName(), l);

	}

	public void fireUpdates(ModelType object)
	{
		final Set<UpdateListener> sublinsteners = new HashSet<UpdateListener>();

		final UpdateListener updateListener = ears.get(object.getClass().getName());
		if (updateListener != null)
		{
			sublinsteners.add(updateListener);
		}

		for (final UpdateListener sublinstener : sublinsteners)
		{
			sublinstener.updated(object);
		}

	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends ModelType> T findByName(String name, Class<T> clazz)
	{
		T result = null;
		final String findQuery = "select u from " + clazz.getName() + " u where u.name = :name";
		if (getEntityManager() != null)
		{
			final Query query = getEntityManager().createQuery(findQuery);
			query.setParameter("name", name);
			final List<T> tResult = query.getResultList();
			if (tResult.isEmpty() == false)
			{
				result = tResult.get(0);
			}
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public ModelType findByName(String name)
	{

		ModelType result = null;

		for (final Class<? extends ModelType> clazz : getEntityTypes())
		{
			final String queryString = "select o from " + clazz.getName() + "  o where o.name = :name";
			final Query query = entityManager.createQuery(queryString);
			query.setParameter("name", name);

			final List<Object> results = query.getResultList();
			if (results.isEmpty() == false)
			{
				result = (ModelType) results.get(0);
				break;
			}
		}

		return result;
	}
	
	
	private Set<Class<? extends ModelType>> getEntityTypes()
	{
	    Set<Class<? extends ModelType>> result = new HashSet<Class<? extends ModelType>>();
	    for (Class<? extends ModelType> type : types)
	    {
	        if(type.isAnnotationPresent(Entity.class) )
	        {
	            result.add(type);
	        }
	        
	    }
	    return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends ModelType> Set<T> findAll(Class<T> clazz)
	{
		Set<T> result = new HashSet<T>();
		final String queryString = "select o from " + clazz.getName() + "  o ";
		final Query query = entityManager.createQuery(queryString);
		result.addAll(query.getResultList());
		return result;
	}

	
    @Override
    public Object getTransactionalProxy(Object obj)
    {
        TransactionalProxyClass transactionalProxyClass = new TransactionalProxyClass(obj);
        return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), transactionalProxyClass);
    }
	
    @SuppressWarnings("unchecked")
    @Override
    public  <T, O extends Object >  T getTransactionalProxy(O obj, Class<T> clazz)
    {
        TransactionalProxyClass transactionalProxyClass = new TransactionalProxyClass(obj);
        return (T) java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), transactionalProxyClass);
    }
    
    
	private class TransactionalProxyClass implements java.lang.reflect.InvocationHandler
	{
	    Object obj;

	    public TransactionalProxyClass(Object obj)
	    {
	        this.obj = obj;
	    }
	    
	    
	    
	  

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            Object result;
            try
            {
                PersistServiceImpl.this.begin();
                result = method.invoke(obj, args);
                PersistServiceImpl.this.commit();
            } 
            catch (InvocationTargetException e)
            {
                PersistServiceImpl.this.rollback();
                throw e;
            }
            catch (RuntimeException r)
            {
                PersistServiceImpl.this.rollback();
                throw r;
            }         
            catch (Throwable e)
            {
                PersistServiceImpl.this.rollback();
                throw e;

            }
            return result;

        }
    }

	
	
	
}















